<?php
/**
 * Created by PhpStorm.
 * User: Apple
 * Date: 2018/11/1 0001
 * Time: 14:47
 */
namespace App\WebSocket;

use EasySwoole\EasySwoole\Task\TaskManager;
use App\Utility\BaseEnum;
use App\Common\AuthCommon;
use EasySwoole\Component\Pool\PoolManager;
use App\Utility\Pool\RedisPool;
use App\Utility\Pool\MySQLPool;
use App\Utility\RedisTools;
use App\Enum\SysEnum;
use App\Common\PlayCommon;

/**
 * TODO Class WebSocketEvent
 * 此类是 WebSocet 中一些非强制的自定义事件处理
 *
 * @package App\WebSocket
 */
class WebSocketEvent
{
    /**
     * TODO 握手事件
     *
     * @param object $request
     * @param object $response
     * @return bool
     */
    public static function onHandShake(\swoole_http_request $request, \swoole_http_response $response)
    {
        $clientFd = $request->fd;
        /**
         * TODO 此处自定义握手规则 返回 false 时中止握手
         */
        if (!self::customHandShake($request, $response)) {
            $response->end();
            return false;
        }

        /**
         * TODO 此处是 RFC规范中的WebSocket握手验证过程 必须执行 否则无法正确握手
         */
        if (self::secWebsocketAccept($request, $response)) {
            $response->end();
            if(isset($clientFd) && $clientFd > 0 ){
                BaseEnum::Logger('异步发送心跳检测试PING: Fd->' . $clientFd, 'user-login');
                TaskManager::getInstance()->async(function() use ($clientFd){
                    AuthCommon::pushFrontWeb($clientFd,json_encode(['m'=>BaseEnum::XYM_0X04,'data'=>0x1]));  //握手成功后即发送一次心跳数据激活
                });
            }
            return TRUE;
        }
        $response->end();
        return FALSE;
    }

    /**
     * TODO 自定义握手事件
     *
     * @param object $request
     * @param object $response
     * @return bool
     */
    private static function customHandShake(\swoole_http_request $request, \swoole_http_response $response): bool
    {
        /**
         * TODO 这里可以通过 http request 获取到相应的数据
         * TO 进行自定义验证后即可
         * (注) 浏览器中 JavaScript 并不支持自定义握手请求头 只能选择别的方式 如get参数
         */
        // $headers = $request->header;
        // $cookie = $request->cookie;
        // if (如果不满足我某些自定义的需求条件，返回false，握手失败) {
        // return false;
        // }
        return true;
    }

    /**
     * RFC规范中的WebSocket握手验证过程
     * TO 以下内容必须强制使用
     *
     * @param object $request
     * @param object $response
     * @return bool
     */
    private static function secWebsocketAccept(\swoole_http_request $request, \swoole_http_response $response): bool
    {
        // ws rfc 规范中约定的验证过程
        if (! isset($request->header['sec-websocket-key'])) {
            // 需要 Sec-WebSocket-Key 如果没有拒绝握手
            var_dump('shake fai1 3');
            return false;
        }
        if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $request->header['sec-websocket-key']) || 16 !== strlen(base64_decode($request->header['sec-websocket-key']))) {
            var_dump('shake fai1 4'); // 不接受握手
            return false;
        }

        $key = base64_encode(sha1($request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        $headers = array(
            'Upgrade' => 'websocket',
            'Connection' => 'Upgrade',
            'Sec-WebSocket-Accept' => $key,
            'Sec-WebSocket-Version' => '13',
            'KeepAlive' => 'off'
        );

        if (isset($request->header['sec-websocket-protocol'])) {
            $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
        }

        // 发送验证后的header
        foreach ($headers as $key => $val) {
            $response->header($key, $val);
        }

        // 接受握手 还需要101状态码以切换状态
        $response->status(101);
        // var_dump('shake success at fd :' . $request->fd);
        return true;
    }
    
    
    
    #####START EVENT#####
    
    /**
     * TODO 用户连接到WebSocket
     * @param object $server 服务(\swoole_server)
     * @param object $req  请求(swoole_http_request)
     */
    public static function onOpen($server, $request)
    {
        // onOpen事件
        // $token = $request->get['token'];
        // $server->close($request->fd);
        return true;
    }
    
    /**
     * TODO 主服务注册
     * TODO 注册数据库协程连接池(onWorkerStart)
     * @param object $server
     * @param int $workerId
     * @return boolean
     */
    public static function onWorkerStart($server,$workerId)
    {
        BaseEnum::Logger('创建线程：workerid=' . $workerId."-Get= ". json_encode(["now_time"=> BaseEnum::getNowTime(true)]), 'user-login'); 
        if ($server->taskworker == false) {
            PoolManager::getInstance()->getPool(RedisPool::class)->preLoad(10);         // 预创建数量,必须小于连接池最大数量
            PoolManager::getInstance()->getPool(MySQLPool::class)->preLoad(10);         // 预创建连接最小MYSQL池连接数
            $swooleIp = swoole_get_local_ip();
            BaseEnum::Logger("获取IP：".json_encode($swooleIp) . ", 执行异步： Data：" . BaseEnum::getNowTime(true), 'user-login');
            
            //TODO 机器人代理ID和创建个数
            $roboNum = 300;
            $robotAgentId = 10010;
            
            //TODO 实例化任务模板类 并将数据带进去 可以在任务类$taskData参数拿到数据
            TaskManager::getInstance()->async(function ()  {
                echo "清空REDIS所有缓存数据======>\n";
                $delKeys  = SysEnum::CLEAR_SERVER_REDISKeys;
                PlayCommon::clearAllRobotPlayer();      //TODO 清理机器人玩家
                AuthCommon::startGameServer();          //TODO 启动服务器时更新服务器配置
                RedisTools::Pool_FlushAll($delKeys);    //TODO 清理所有REDIS的相关数据
                return true;
            }, function () use ($robotAgentId,$roboNum) {
                echo "异步任务执行完毕============>\n开始启动{$roboNum}个机器人玩家信息（AgentId={$robotAgentId}）";
                //TODO 启动1000个机器人玩家正式登录和安排使用
                PlayCommon::initRobotLoginGame($robotAgentId,$roboNum);
            });
        }
        return true;
    }
    
    
    
    /**
     * TODO 用户链接事件
     * @param $server object    服务(\swoole_server)
     * @param int $fd           用户fd
     * @param int $reactorId    进程
     */
    public static function onConnect($server, int $fd, int $reactorId)
    {
        BaseEnum::Logger('创建连接[onConnect]：Fd=' . $fd . ',Workid===' . $reactorId . ' Connect SUCCESS!! ===> ' . @$_SERVER['SSH_CLIENT'], 'user-login');
        return true;
    }
    
    /**
     * TODO 用户断开链接
     * @param $server object 服务(\swoole_server)
     * @param int $fd 用户fd
     * @param int $reactorId 进程
     */
    public static function onClose($server, int $fd, int $reactorId)
    {
        $params = ["fd"=> $fd,"workid"=> $reactorId];
        $clientInfo = $server-> getClientInfo($fd);
        unset($clientInfo['connect_time'],$clientInfo['last_time'],$clientInfo['close_errno']);
        BaseEnum::Logger("触发onWsClose事件：连接客户端(FD={$fd},workerId={$reactorId}),Client=". json_encode($clientInfo), 'user-login');
        //退出时要做异步处理
        if (isset($clientInfo['websocket_status']) && isset($clientInfo['websocket_status']) && intval($clientInfo['websocket_status']) === WEBSOCKET_STATUS_FRAME) { // 清除全部关联
            if ($reactorId < 0) {
                echo "wss server is closed!!! \n";
            }
            //定时器处理中
            BaseEnum::Logger("异步断线执行任务：Task=". json_encode($params), 'user-login');
            TaskManager::getInstance()->async(function() use($fd){
                AuthCommon::clearLoginFd($fd);
            });
        }
        return true;
    }
    
    #####END EVENT#####
    
}
